/**@file

Copyright (c) 2006 - 2009, Intel Corporation. All rights reserved.<BR>
(C) Copyright 2018 Hewlett Packard Enterprise Development LP<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

Module Name:

  WinNtBusDriver.c

Abstract:

This following section documents the envirnoment variables for the Win NT
build.  These variables are used to define the (virtual) hardware
configuration of the NT environment

A ! can be used to seperate multiple instances in a variable. Each
instance represents a seperate hardware device.

EFI_WIN_NT_PHYSICAL_DISKS - maps to drives on your system
EFI_WIN_NT_VIRTUAL_DISKS  - maps to a device emulated by a file
EFI_WIN_NT_FILE_SYSTEM    - mouts a directory as a file system
EFI_WIN_NT_CONSOLE        - make a logical comand line window (only one!)
EFI_WIN_NT_GOP            - Builds GOP Windows of Width and Height
EFI_WIN_NT_SERIAL_PORT    - maps physical serial ports

 <F>ixed       - Fixed disk like a hard drive.
 <R>emovable   - Removable media like a floppy or CD-ROM.
 Read <O>nly   - Write protected device.
 Read <W>rite  - Read write device.
 <block count> - Decimal number of blocks a device supports.
 <block size>  - Decimal number of bytes per block.

 NT envirnonment variable contents. '<' and '>' are not part of the variable,
 they are just used to make this help more readable. There should be no
 spaces between the ';'. Extra spaces will break the variable. A '!' is
 used to seperate multiple devices in a variable.

 EFI_WIN_NT_VIRTUAL_DISKS =
   <F | R><O | W>;<block count>;<block size>[!...]

 EFI_WIN_NT_PHYSICAL_DISKS =
   <drive letter>:<F | R><O | W>;<block count>;<block size>[!...]

 Virtual Disks: These devices use a file to emulate a hard disk or removable
                media device.

   Thus a 20 MB emulated hard drive would look like:
   EFI_WIN_NT_VIRTUAL_DISKS=FW;40960;512

   A 1.44MB emulated floppy with a block size of 1024 would look like:
   EFI_WIN_NT_VIRTUAL_DISKS=RW;1440;1024

 Physical Disks: These devices use NT to open a real device in your system

   Thus a 120 MB floppy would look like:
   EFI_WIN_NT_PHYSICAL_DISKS=B:RW;245760;512

   Thus a standard CD-ROM floppy would look like:
   EFI_WIN_NT_PHYSICAL_DISKS=Z:RO;307200;2048

 EFI_WIN_NT_FILE_SYSTEM =
   <directory path>[!...]

   Mounting the two directories C:\FOO and C:\BAR would look like:
   EFI_WIN_NT_FILE_SYSTEM=c:\foo!c:\bar

 EFI_WIN_NT_CONSOLE =
   <window title>

   Declaring a text console window with the title "My EFI Console" woild look like:
   EFI_WIN_NT_CONSOLE=My EFI Console

 EFI_WIN_NT_GOP =
   <width> <height>[!...]

   Declaring a two GOP windows with resolutions of 800x600 and 1024x768 would look like:
   Example : EFI_WIN_NT_GOP=800 600!1024 768

 EFI_WIN_NT_SERIAL_PORT =
   <port name>[!...]

   Declaring two serial ports on COM1 and COM2 would look like:
   Example : EFI_WIN_NT_SERIAL_PORT=COM1!COM2

 EFI_WIN_NT_PASS_THROUGH =
   <BaseAddress>;<Bus#>;<Device#>;<Function#>

   Declaring a base address of 0xE0000000 (used for PCI Express devices)
   and having NT32 talk to a device located at bus 0, device 1, function 0:
   Example : EFI_WIN_NT_PASS_THROUGH=E000000;0;1;0

---*/

//
// The package level header files this module uses
//
#include <Uefi.h>
#include <WinNtDxe.h>
//
// The protocols, PPI and GUID defintions for this module
//
#include <Protocol/WinNtThunk.h>
#include <Protocol/WinNtIo.h>
#include <Protocol/ComponentName.h>
#include <Protocol/DriverBinding.h>
#include <Protocol/DevicePath.h>
//
// The Library classes this module consumes
//
#include <Library/DebugLib.h>
#include <Library/BaseLib.h>
#include <Library/UefiDriverEntryPoint.h>
#include <Library/UefiLib.h>
#include <Library/PcdLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/DevicePathLib.h>
#include <Library/MemoryAllocationLib.h>

#include "WinNtBusDriver.h"

extern EFI_GUID gWinNtBusDriverGuid;
//
// DriverBinding protocol global
//
EFI_DRIVER_BINDING_PROTOCOL           gWinNtBusDriverBinding = {
  WinNtBusDriverBindingSupported,
  WinNtBusDriverBindingStart,
  WinNtBusDriverBindingStop,
  0xa,
  NULL,
  NULL
};

#define NT_PCD_ARRAY_SIZE (sizeof(mPcdEnvironment)/sizeof(NT_PCD_ENTRY))

//
// Table to map NT Environment variable to the GUID that should be in
// device path.
//
NT_PCD_ENTRY  mPcdEnvironment[] = {
  PcdToken(PcdWinNtConsole),       &gEfiWinNtConsoleGuid,
  PcdToken(PcdWinNtGop),           &gEfiWinNtGopGuid,
  PcdToken(PcdWinNtSerialPort),    &gEfiWinNtSerialPortGuid,
  PcdToken(PcdWinNtFileSystem),    &gEfiWinNtFileSystemGuid,
  PcdToken(PcdWinNtVirtualDisk),   &gEfiWinNtVirtualDisksGuid,
  PcdToken(PcdWinNtPhysicalDisk),  &gEfiWinNtPhysicalDisksGuid
};

/**
  The user Entry Point for module WinNtBusDriver. The user code starts with this function.

  @param[in] ImageHandle    The firmware allocated handle for the EFI image.
  @param[in] SystemTable    A pointer to the EFI System Table.

  @retval EFI_SUCCESS       The entry point is executed successfully.
  @retval other             Some error occurs when executing this entry point.

**/
EFI_STATUS
EFIAPI
InitializeWinNtBusDriver(
  IN EFI_HANDLE           ImageHandle,
  IN EFI_SYSTEM_TABLE     *SystemTable
  )
{
  EFI_STATUS              Status;

  //
  // Install driver model protocol(s).
  //
  Status = EfiLibInstallDriverBindingComponentName2 (
             ImageHandle,
             SystemTable,
             &gWinNtBusDriverBinding,
             ImageHandle,
             &gWinNtBusDriverComponentName,
             &gWinNtBusDriverComponentName2
             );
  ASSERT_EFI_ERROR (Status);


  return Status;
}

VOID *
AllocateMemory (
  IN  UINTN   Size
  )
{
  VOID        *Buffer;

  Buffer = AllocatePool (Size);
  ASSERT (Buffer != NULL);

  return Buffer;
}


EFI_STATUS
EFIAPI
WinNtBusDriverBindingSupported (
  IN  EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                   ControllerHandle,
  IN  EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
/*++

Routine Description:

Arguments:

Returns:

  None

**/
// TODO:    This - add argument and description to function comment
// TODO:    ControllerHandle - add argument and description to function comment
// TODO:    RemainingDevicePath - add argument and description to function comment
// TODO:    EFI_UNSUPPORTED - add return value to function comment
// TODO:    EFI_UNSUPPORTED - add return value to function comment
// TODO:    EFI_SUCCESS - add return value to function comment
// TODO:    EFI_SUCCESS - add return value to function comment
{
  EFI_STATUS                Status;
  EFI_DEVICE_PATH_PROTOCOL  *ParentDevicePath;
  EFI_WIN_NT_THUNK_PROTOCOL *WinNtThunk;
  UINTN                     Index;

  //
  // Check the contents of the first Device Path Node of RemainingDevicePath to make sure
  // it is a legal Device Path Node for this bus driver's children.
  //
  if (RemainingDevicePath != NULL) {
    //
    // Check if RemainingDevicePath is the End of Device Path Node, 
    // if yes, go on checking other conditions
    //
    if (!IsDevicePathEnd (RemainingDevicePath)) {
      //
      // If RemainingDevicePath isn't the End of Device Path Node,
      // check its validation
      //
      if (RemainingDevicePath->Type != HARDWARE_DEVICE_PATH ||
          RemainingDevicePath->SubType != HW_VENDOR_DP ||
          DevicePathNodeLength(RemainingDevicePath) != sizeof(WIN_NT_VENDOR_DEVICE_PATH_NODE)) {
        return EFI_UNSUPPORTED;
      }
  
      for (Index = 0; Index < NT_PCD_ARRAY_SIZE; Index++) {
        if (CompareGuid (&((VENDOR_DEVICE_PATH *) RemainingDevicePath)->Guid, mPcdEnvironment[Index].DevicePathGuid)) {
          break;
        }
      }
  
      if (Index >= NT_PCD_ARRAY_SIZE) {
        return EFI_UNSUPPORTED;
      }
    }
  }

  //
  // Open the IO Abstraction(s) needed to perform the supported test
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiWinNtThunkProtocolGuid,
                  (VOID **) &WinNtThunk,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (Status == EFI_ALREADY_STARTED) {
    return EFI_SUCCESS;
  }

  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Close the I/O Abstraction(s) used to perform the supported test
  //
  gBS->CloseProtocol (
        ControllerHandle,
        &gEfiWinNtThunkProtocolGuid,
        This->DriverBindingHandle,
        ControllerHandle
        );

  //
  // Open the EFI Device Path protocol needed to perform the supported test
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &ParentDevicePath,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (Status == EFI_ALREADY_STARTED) {
    return EFI_SUCCESS;
  }

  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Since we call through WinNtThunk we need to make sure it's valid
  //
  Status = EFI_SUCCESS;
  if (WinNtThunk->Signature != EFI_WIN_NT_THUNK_PROTOCOL_SIGNATURE) {
    Status = EFI_UNSUPPORTED;
  }

  //
  // Close protocol, don't use device path protocol in the Support() function
  //
  gBS->CloseProtocol (
        ControllerHandle,
        &gEfiDevicePathProtocolGuid,
        This->DriverBindingHandle,
        ControllerHandle
        );

  return Status;
}

EFI_STATUS
EFIAPI
WinNtBusDriverBindingStart (
  IN  EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                   ControllerHandle,
  IN  EFI_DEVICE_PATH_PROTOCOL     *RemainingDevicePath
  )
/*++

Routine Description:

Arguments:

Returns:

  None

--*/
// TODO:    This - add argument and description to function comment
// TODO:    ControllerHandle - add argument and description to function comment
// TODO:    RemainingDevicePath - add argument and description to function comment
// TODO:    EFI_OUT_OF_RESOURCES - add return value to function comment
// TODO:    EFI_OUT_OF_RESOURCES - add return value to function comment
// TODO:    EFI_SUCCESS - add return value to function comment
{
  EFI_STATUS                      Status;
  EFI_WIN_NT_THUNK_PROTOCOL       *WinNtThunk;
  EFI_DEVICE_PATH_PROTOCOL        *ParentDevicePath;
  WIN_NT_BUS_DEVICE               *WinNtBusDevice;
  WIN_NT_IO_DEVICE                *WinNtDevice;
  UINTN                           Index;
  CHAR16                          *StartString;
  CHAR16                          *SubString;
  UINT16                          Count;
  UINTN                           StringSize;
  UINT16                          ComponentName[MAX_NT_ENVIRNMENT_VARIABLE_LENGTH];
  WIN_NT_VENDOR_DEVICE_PATH_NODE  *Node;
  BOOLEAN                         CreateDevice;
  CHAR16                          *TempStr;
  CHAR16                          *PcdTempStr;
  UINTN                           TempStrSize;

  Status = EFI_UNSUPPORTED;

  //
  // Grab the protocols we need
  //
  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiDevicePathProtocolGuid,
                  (VOID **) &ParentDevicePath,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status) && Status != EFI_ALREADY_STARTED) {
    return Status;
  }

  Status = gBS->OpenProtocol (
                  ControllerHandle,
                  &gEfiWinNtThunkProtocolGuid,
                  (VOID **) &WinNtThunk,
                  This->DriverBindingHandle,
                  ControllerHandle,
                  EFI_OPEN_PROTOCOL_BY_DRIVER
                  );
  if (EFI_ERROR (Status) && Status != EFI_ALREADY_STARTED) {
    return Status;
  }

  if (Status != EFI_ALREADY_STARTED) {
    WinNtBusDevice = AllocatePool (sizeof (WIN_NT_BUS_DEVICE));
    if (WinNtBusDevice == NULL) {
      return EFI_OUT_OF_RESOURCES;
    }

    WinNtBusDevice->Signature           = WIN_NT_BUS_DEVICE_SIGNATURE;
    WinNtBusDevice->ControllerNameTable = NULL;

    AddUnicodeString2 (
      "eng",
      gWinNtBusDriverComponentName.SupportedLanguages,
      &WinNtBusDevice->ControllerNameTable,
      L"Windows Bus Controller",
      TRUE
      );
    AddUnicodeString2 (
      "en",
      gWinNtBusDriverComponentName2.SupportedLanguages,
      &WinNtBusDevice->ControllerNameTable,
      L"Windows Bus Controller",
      FALSE
      );


    Status = gBS->InstallMultipleProtocolInterfaces (
                    &ControllerHandle,
                    &gWinNtBusDriverGuid,
                    WinNtBusDevice,
                    NULL
                    );
    if (EFI_ERROR (Status)) {
      FreeUnicodeStringTable (WinNtBusDevice->ControllerNameTable);
      FreePool (WinNtBusDevice);
      return Status;
    }
  }

  //
  // Loop on the Variable list. Parse each variable to produce a set of handles that
  // represent virtual hardware devices.
  //
  for (Index = 0; Index < NT_PCD_ARRAY_SIZE; Index++) {
    PcdTempStr = (VOID *)LibPcdGetPtr (mPcdEnvironment[Index].Token);
    ASSERT (PcdTempStr != NULL);

    TempStrSize = StrLen (PcdTempStr);
    TempStr = AllocateMemory (((TempStrSize + 1) * sizeof (CHAR16)));
    StrCpy (TempStr, PcdTempStr);

    StartString = TempStr;

    //
    // Parse the envirnment variable into sub strings using '!' as a delimator.
    // Each substring needs it's own handle to be added to the system. This code
    // does not understand the sub string. Thats the device drivers job.
    //
    Count = 0;
    while (*StartString != '\0') {

      //
      // Find the end of the sub string
      //
      SubString = StartString;
      while (*SubString != '\0' && *SubString != '!') {
        SubString++;
      }

      if (*SubString == '!') {
        //
        // Replace token with '\0' to make sub strings. If this is the end
        //  of the string SubString will already point to NULL.
        //
        *SubString = '\0';
        SubString++;
      }

      CreateDevice = TRUE;
      if (RemainingDevicePath != NULL) {
        CreateDevice  = FALSE;
        //
        // Check if RemainingDevicePath is the End of Device Path Node, 
        // if yes, don't create any child device 
        //
        if (!IsDevicePathEnd (RemainingDevicePath)) {
          //
          // If RemainingDevicePath isn't the End of Device Path Node,
          // check its validation
          //
          Node          = (WIN_NT_VENDOR_DEVICE_PATH_NODE *) RemainingDevicePath;
          if (Node->VendorDevicePath.Header.Type == HARDWARE_DEVICE_PATH &&
              Node->VendorDevicePath.Header.SubType == HW_VENDOR_DP &&
              DevicePathNodeLength (&Node->VendorDevicePath.Header) == sizeof (WIN_NT_VENDOR_DEVICE_PATH_NODE)
              ) {
            if (CompareGuid (&Node->VendorDevicePath.Guid, mPcdEnvironment[Index].DevicePathGuid) &&
                Node->Instance == Count
                ) {
              CreateDevice = TRUE;
            }
          }
        }
      }

      if (CreateDevice) {

        //
        // Allocate instance structure, and fill in parent information.
        //
        WinNtDevice = AllocateMemory (sizeof (WIN_NT_IO_DEVICE));
        if (WinNtDevice == NULL) {
          return EFI_OUT_OF_RESOURCES;
        }

        WinNtDevice->Handle             = NULL;
        WinNtDevice->ControllerHandle   = ControllerHandle;
        WinNtDevice->ParentDevicePath   = ParentDevicePath;

        WinNtDevice->WinNtIo.WinNtThunk = WinNtThunk;

        //
        // Plus 2 to account for the NULL at the end of the Unicode string
        //
        StringSize = (UINTN) ((UINT8 *) SubString - (UINT8 *) StartString) + sizeof (CHAR16);
        WinNtDevice->WinNtIo.EnvString = AllocateMemory (StringSize);
        if (WinNtDevice->WinNtIo.EnvString != NULL) {
          CopyMem (WinNtDevice->WinNtIo.EnvString, StartString, StringSize);
        }

        WinNtDevice->ControllerNameTable = NULL;

        WinNtThunk->SPrintf (ComponentName, sizeof (ComponentName), L"%s", WinNtDevice->WinNtIo.EnvString);

        WinNtDevice->DevicePath = WinNtBusCreateDevicePath (
                                    ParentDevicePath,
                                    mPcdEnvironment[Index].DevicePathGuid,
                                    Count
                                    );
        if (WinNtDevice->DevicePath == NULL) {
          FreePool (WinNtDevice);
          return EFI_OUT_OF_RESOURCES;
        }

        AddUnicodeString2 (
          "eng",
          gWinNtBusDriverComponentName.SupportedLanguages,
          &WinNtDevice->ControllerNameTable,
          ComponentName,
          TRUE
          );
        AddUnicodeString2 (
          "en",
          gWinNtBusDriverComponentName2.SupportedLanguages,
          &WinNtDevice->ControllerNameTable,
          ComponentName,
          FALSE
          );


        WinNtDevice->WinNtIo.TypeGuid       = mPcdEnvironment[Index].DevicePathGuid;
        WinNtDevice->WinNtIo.InstanceNumber = Count;

        WinNtDevice->Signature              = WIN_NT_IO_DEVICE_SIGNATURE;

        Status = gBS->InstallMultipleProtocolInterfaces (
                        &WinNtDevice->Handle,
                        &gEfiDevicePathProtocolGuid,
                        WinNtDevice->DevicePath,
                        &gEfiWinNtIoProtocolGuid,
                        &WinNtDevice->WinNtIo,
                        NULL
                        );
        if (EFI_ERROR (Status)) {
          FreeUnicodeStringTable (WinNtDevice->ControllerNameTable);
          FreePool (WinNtDevice);
        } else {
          //
          // Open For Child Device
          //
          Status = gBS->OpenProtocol (
                          ControllerHandle,
                          &gEfiWinNtThunkProtocolGuid,
                          (VOID **) &WinNtThunk,
                          This->DriverBindingHandle,
                          WinNtDevice->Handle,
                          EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
                          );
        }
      }

      //
      // Parse Next sub string. This will point to '\0' if we are at the end.
      //
      Count++;
      StartString = SubString;
    }

    FreePool (TempStr);
  }

  return EFI_SUCCESS;
}


EFI_STATUS
EFIAPI
WinNtBusDriverBindingStop (
  IN  EFI_DRIVER_BINDING_PROTOCOL  *This,
  IN  EFI_HANDLE                   ControllerHandle,
  IN  UINTN                        NumberOfChildren,
  IN  EFI_HANDLE                   *ChildHandleBuffer
  )
/*++

Routine Description:

Arguments:

Returns:

    None

--*/
// TODO:    This - add argument and description to function comment
// TODO:    ControllerHandle - add argument and description to function comment
// TODO:    NumberOfChildren - add argument and description to function comment
// TODO:    ChildHandleBuffer - add argument and description to function comment
// TODO:    EFI_SUCCESS - add return value to function comment
// TODO:    EFI_DEVICE_ERROR - add return value to function comment
// TODO:    EFI_SUCCESS - add return value to function comment
{
  EFI_STATUS                Status;
  UINTN                     Index;
  BOOLEAN                   AllChildrenStopped;
  EFI_WIN_NT_IO_PROTOCOL    *WinNtIo;
  WIN_NT_BUS_DEVICE         *WinNtBusDevice;
  WIN_NT_IO_DEVICE          *WinNtDevice;
  EFI_WIN_NT_THUNK_PROTOCOL *WinNtThunk;

  //
  // Complete all outstanding transactions to Controller.
  // Don't allow any new transaction to Controller to be started.
  //

  if (NumberOfChildren == 0) {
    //
    // Close the bus driver
    //
    Status = gBS->OpenProtocol (
                    ControllerHandle,
                    &gWinNtBusDriverGuid,
                    (VOID **) &WinNtBusDevice,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    gBS->UninstallMultipleProtocolInterfaces (
          ControllerHandle,
          &gWinNtBusDriverGuid,
          WinNtBusDevice,
          NULL
          );

    FreeUnicodeStringTable (WinNtBusDevice->ControllerNameTable);

    FreePool (WinNtBusDevice);

    gBS->CloseProtocol (
          ControllerHandle,
          &gEfiWinNtThunkProtocolGuid,
          This->DriverBindingHandle,
          ControllerHandle
          );

    gBS->CloseProtocol (
          ControllerHandle,
          &gEfiDevicePathProtocolGuid,
          This->DriverBindingHandle,
          ControllerHandle
          );
    return EFI_SUCCESS;
  }

  AllChildrenStopped = TRUE;

  for (Index = 0; Index < NumberOfChildren; Index++) {

    Status = gBS->OpenProtocol (
                    ChildHandleBuffer[Index],
                    &gEfiWinNtIoProtocolGuid,
                    (VOID **) &WinNtIo,
                    This->DriverBindingHandle,
                    ControllerHandle,
                    EFI_OPEN_PROTOCOL_GET_PROTOCOL
                    );
    if (!EFI_ERROR (Status)) {

      WinNtDevice = WIN_NT_IO_DEVICE_FROM_THIS (WinNtIo);

      Status = gBS->CloseProtocol (
                      ControllerHandle,
                      &gEfiWinNtThunkProtocolGuid,
                      This->DriverBindingHandle,
                      WinNtDevice->Handle
                      );

      Status = gBS->UninstallMultipleProtocolInterfaces (
                      WinNtDevice->Handle,
                      &gEfiDevicePathProtocolGuid,
                      WinNtDevice->DevicePath,
                      &gEfiWinNtIoProtocolGuid,
                      &WinNtDevice->WinNtIo,
                      NULL
                      );

      if (EFI_ERROR (Status)) {
        gBS->OpenProtocol (
              ControllerHandle,
              &gEfiWinNtThunkProtocolGuid,
              (VOID **) &WinNtThunk,
              This->DriverBindingHandle,
              WinNtDevice->Handle,
              EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER
              );
      } else {
        //
        // Close the child handle
        //
        FreeUnicodeStringTable (WinNtDevice->ControllerNameTable);
        FreePool (WinNtDevice);
      }
    }

    if (EFI_ERROR (Status)) {
      AllChildrenStopped = FALSE;
    }
  }

  if (!AllChildrenStopped) {
    return EFI_DEVICE_ERROR;
  }

  return EFI_SUCCESS;
}

EFI_DEVICE_PATH_PROTOCOL *
WinNtBusCreateDevicePath (
  IN  EFI_DEVICE_PATH_PROTOCOL  *RootDevicePath,
  IN  EFI_GUID                  *Guid,
  IN  UINT16                    InstanceNumber
  )
/*++

Routine Description:
  Create a device path node using Guid and InstanceNumber and append it to
  the passed in RootDevicePath

Arguments:
  RootDevicePath - Root of the device path to return.

  Guid           - GUID to use in vendor device path node.

  InstanceNumber - Instance number to use in the vendor device path. This
                    argument is needed to make sure each device path is unique.

Returns:

  EFI_DEVICE_PATH_PROTOCOL

--*/
{
  WIN_NT_VENDOR_DEVICE_PATH_NODE  DevicePath;

  DevicePath.VendorDevicePath.Header.Type     = HARDWARE_DEVICE_PATH;
  DevicePath.VendorDevicePath.Header.SubType  = HW_VENDOR_DP;
  SetDevicePathNodeLength (&DevicePath.VendorDevicePath.Header, sizeof (WIN_NT_VENDOR_DEVICE_PATH_NODE));

  //
  // The GUID defines the Class
  //
  CopyMem (&DevicePath.VendorDevicePath.Guid, Guid, sizeof (EFI_GUID));

  //
  // Add an instance number so we can make sure there are no Device Path
  // duplication.
  //
  DevicePath.Instance = InstanceNumber;

  return AppendDevicePathNode (
          RootDevicePath,
          (EFI_DEVICE_PATH_PROTOCOL *) &DevicePath
          );
}
